/*
 Xern Entity Relocation Network
 Copyright (C) 2011 Ronie Salgado <roniesalg@gmail.com>

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
*/

// This terrain decompressor is heavily based in the implementation of
// libopenmetaverse, class TerrainCompressor.

#include "Xern/MathCommon.hpp"
#include "TerrainCodec.hpp"

namespace XernMetaverse
{
	const int EndOfPatches = 97;

	TerrainCodec::TerrainCodec()
	{
		BuildTables16();
	}

	TerrainCodec::~TerrainCodec()
	{
	}

	void TerrainCodec::BuildTables16()
	{
		const float hposz = X_PI * 0.5f/16.0f;

		// Build the cosine table.
		for(int u = 0; u < 16; u++)
		{
			for(int n = 0; n < 16; n++)
				cosineTable16[u*16 + n] = cos((2.0f * float(n) + 1.0f) * float(u) * hposz);
		}

		// Build the dequantize table.
		for(int j = 0; j < 16; j++)
		{
			for(int i = 0; i < 16; i++)
				dequantizeTable16[j*16 + i] = 1.0f + 2.0f*float(i+j);
		}

		// Build the copy matrix.
        bool diag = false;
        bool right = true;
        int i = 0;
        int j = 0;
        int count = 0;

        while (i < 16 && j < 16)
        {
        	copyMatrix16[j * 16 + i] = count++;

            if (!diag)
            {
                if (right)
                {
                    if (i < 16 - 1) i++;
                    else j++;

                    right = false;
                    diag = true;
                }
                else
                {
                    if (j < 16 - 1) j++;
                    else i++;

                    right = true;
                    diag = true;
                }
            }
            else
            {
                if (right)
                {
                    i++;
                    j--;
                    if (i == 16 - 1 || j == 0) diag = false;
                }
                else
                {
                    i--;
                    j++;
                    if (j == 16 - 1 || i == 0) diag = false;
                }
            }
        }
	}

	void TerrainCodec::DecodeLand(size_t srcLen, const void *src, size_t dstLen, float *dst)
	{
		assert(dstLen == 256*256);

		// Create the bit stream.
		BitStream in(srcLen, (void*)src);

		// dump the layer.
		FILE *f = fopen("layer.dump", "wb");
		fwrite(src, srcLen, 1, f);
		fclose(f);

		// Decode the patch group header.
		PatchGroupHeader groupHeader;
		groupHeader.stride = in.UnpackUInt(16);
		groupHeader.patchSize = in.UnpackUInt(8);
		groupHeader.type = LayerType(in.UnpackUInt(8));

		// Decode the patches.
		while(in.GetPosition() < srcLen)
		{
			DecodePatch(in, groupHeader, dstLen, dst);
		}
	}

	const float InvSqrt2 = 0.7071067811865475244008443621049f;
	void TerrainCodec::IDCT_Column16(float *in, float *out, int column)
	{
		for(int n = 0; n < 16; n++)
		{
			float total = InvSqrt2 * in[column];

			for(int u = 1; u < 16; u++)
			{
				int usize = u * 16;
				total += in[usize + column] * cosineTable16[usize + n];
			}

			out[16*n + column] = total;
		}
	}

	void TerrainCodec::IDCT_Line16(float *in, float *out, int line)
	{
		const float oosob = 2.0f / 16.0f; // ???
		int lineSize = line*16;
		for(int n = 0; n < 16; n++)
		{
			float total = InvSqrt2 * in[lineSize];

			for(int u = 1; u < 16; u++)
				total += in[lineSize + u] * cosineTable16[u*16 + n];

			out[lineSize + n] = total * oosob;
		}
	}

	bool TerrainCodec::DecodePatch(BitStream &in, PatchGroupHeader &groupHeader, size_t heightmapSize, float *heightMap)
	{
		assert(heightmapSize == 256*256);

		// Decode the patch header.
		PatchHeader header;

		// Quantized word bits
		header.quantWordBits = in.UnpackUInt(8);
		if(header.quantWordBits == EndOfPatches)
			return false;

		// DC offset.
		header.dcOffset = in.UnpackFloat();

		// Range
		header.range = in.UnpackUInt(16);

		// Patch ids.
		header.patchIDs = in.UnpackUInt(10);

		// Word bits.
		header.wordBits = (header.quantWordBits & 0x0f) + 2;

		// Decode the patch data.
		int patchData[32*32]; // 32 is the max supported patch size.
		int size = groupHeader.patchSize;
		assert(size == 16 || size == 32);
		for(int i = 0; i < size*size; i++)
		{
			// ?
			int temp = in.UnpackUInt(1);
			if(temp)
			{
				// Value or EOB
				temp = in.UnpackUInt(1);
				if(temp)
				{
					// Value
					temp = in.UnpackUInt(1);
					if(temp)
					{
						// Negative
						temp = in.UnpackUInt(header.wordBits);
						patchData[i] = -temp;
					}
					else
					{
						// Positive
						temp = in.UnpackUInt(header.wordBits);
						patchData[i] = temp;
					}
				}
				else
				{
					// Set the rest to to zero.
					for(int j = i; j < size*size; j++)
						patchData[j] = 0;
					break;
				}
			}
			else
			{
				patchData[i] = 0;
			}
		}

		// Decompress the block.
		float block[32*32];
		float tempBlock[32*32];
		int prequant = (header.quantWordBits >> 4) + 2;
		int quantize = 1 << prequant;
		float invQuant = 1.0f/quantize;
		float mult = invQuant * header.range;
		float add = mult * float(1 << (prequant -1)) + header.dcOffset;

		if(groupHeader.patchSize == 16)
		{
			for(int n = 0; n < 16*16; n++)
				block[n] = patchData[copyMatrix16[n]] * dequantizeTable16[n];

			for(int i = 0; i < 16; i++)
				IDCT_Column16(block, tempBlock, i);

			for(int i = 0; i < 16; i++)
				IDCT_Line16(tempBlock, block, i);
		}
		else
		{
			// TODO: Implement this.
			for(int i = 0; i < 32*32; i++)
				block[i] = 0.0f;
		}

		for(int i = 0; i < size*size; i++)
			block[i] = block[i] * mult + add;

		// Copy the block into the height map.
		int x = header.patchIDs >> 5;
		int y = header.patchIDs & 0x1f;

		// Copy the patch.
		for(int j = 0; j < 16; j++)
		{
			size_t outpos = (y*16 + j)*256 + x*16;
			size_t srcpos = j*16;
			for(int i = 0; i < 16; i++)
				heightMap[outpos++] = block[srcpos++];
		}

		return true;
	}

}; // namespace XernMetaverse
